package com.mall.seckill_mall.service.impl;

import com.mall.seckill_mall.common.RedisPreffix;
import com.mall.seckill_mall.common.Result;
import com.mall.seckill_mall.dao.GoodsDao;
import com.mall.seckill_mall.entity.Goods;
import com.mall.seckill_mall.enums.CodeMessage;
import com.mall.seckill_mall.exception.GoodsException;
import com.mall.seckill_mall.exception.SeckillException;
import com.mall.seckill_mall.service.GoodsService;
import com.mall.seckill_mall.utils.LockUtil;
import com.mall.seckill_mall.utils.ResultUtil;
import com.mall.seckill_mall.vo.GoodsList;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Service
public class GoodsServiceImpl implements GoodsService {

    @Autowired
    private GoodsDao goodsDao;

    @Autowired
    private RedisTemplate<String, Serializable> redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private void insertGoodsToRedis(Goods goods) {
        String redisKey = RedisPreffix.GOODS_PREFFIX.concat(goods.getGoodsId());
        redisTemplate.opsForValue().set(redisKey, goods, 7L, TimeUnit.DAYS);
    }

    @Override
    public String createGoods(Goods goods) {
        boolean ok = goodsDao.insertOne(goods) == 1;
        if(!ok) {
            throw new GoodsException(CodeMessage.DB_OP_EERROR);
        }
        insertGoodsToRedis(goods);
        return goods.getGoodsId();
    }

    @Override
    public void deleteGoods(String goodsId) {
        goodsDao.deleteOne(goodsId);
        String redisKey = RedisPreffix.GOODS_PREFFIX.concat(goodsId);
        redisTemplate.delete(redisKey);
        redisTemplate.opsForHash().delete(RedisPreffix.GOODS_LIST_PREFFIX, goodsId);
    }

    @Override
    public void updateGoods(Goods goods) {
        boolean ok = goodsDao.updateOne(goods) == 1;
        if(!ok) {
            throw new GoodsException(CodeMessage.SERVER_ERROR);
        }
        String redisKey = RedisPreffix.GOODS_PREFFIX.concat(goods.getGoodsId());
        redisTemplate.opsForValue().set(redisKey, goods, 1, TimeUnit.DAYS);
        redisTemplate.opsForHash().put(RedisPreffix.GOODS_LIST_PREFFIX, goods.getGoodsId(), goods);
        insertGoodsToRedis(goods);
    }

    @Override
    public List<GoodsList> selectOnlyUp() throws GoodsException{
        Map<Object, Object> map = redisTemplate.opsForHash().entries(RedisPreffix.GOODS_LIST_PREFFIX);
        List<GoodsList> goodsLists = new ArrayList<>();
        if(map == null || map.isEmpty()) {
            goodsLists = goodsDao.selectOnlyUp();
            if (goodsLists == null || goodsLists.isEmpty()) {
                throw new GoodsException(CodeMessage.EMPTY_GOODS);
            }
            System.out.println("put redis");
            for(GoodsList curGoods : goodsLists) {
                redisTemplate.opsForHash().put(RedisPreffix.GOODS_LIST_PREFFIX, curGoods.getGoodsId(), curGoods);
            }
        } else {
            Set<Object> keys
                    =  redisTemplate.opsForHash().keys(RedisPreffix.GOODS_LIST_PREFFIX);
            List<Integer> strKeys = new ArrayList<>();
            for (Object key : keys) {
                strKeys.add(Integer.parseInt((String) key));
            }
            System.out.println(strKeys.toString());
            Collections.sort(strKeys);
            System.out.println(strKeys.toString());
            for (Integer k : strKeys) {
                goodsLists.add((GoodsList) map.get(String.valueOf(k)));
            }
        }
        return goodsLists;
    }

    @Override
    public List<GoodsList> selectSalersGoods(String salerId) {
        return null;
    }

    @Override
    public Goods getGoods(String goodsId) {
        String redisKey = RedisPreffix.GOODS_PREFFIX.concat(goodsId);
        Goods goods = (Goods) redisTemplate.opsForValue().get(redisKey);
        if(goods == null) {
            goods = goodsDao.selectOne(goodsId);
            redisTemplate.opsForValue().set(redisKey, goods, 1, TimeUnit.DAYS);
        }
        return goods;
    }

    @Override
    public void addGoodsList(String listKey, GoodsList goodsList) {
        redisTemplate.opsForList().rightPush(listKey, goodsList);
        redisTemplate.expire(listKey, 1, TimeUnit.DAYS);
    }

    @Override
    public void addGoodsStock(String goodsId, int stock) {
        String redisKey = RedisPreffix.STOCK_PREFFIX.concat(goodsId);
        int currentStock = (int) redisTemplate.opsForValue().get(redisKey);
        redisTemplate.opsForValue().set(redisKey, currentStock + 1, 1, TimeUnit.DAYS);
    }

    /**
     * 锁定库存加一，代表当前有刚下单未支付的订单
     */
    @Override
    public int preReduceStock(String goodsId) {
        return goodsDao.addCachedStock(goodsId);
    }

    /**
     * 锁定库存减一，代表当前有刚下单未支付的订单因为超时未支付或支付失败而关闭订单
     */
    @Override
    public int releaseStock(String goodsId) {
        return goodsDao.reduceCachedStock(goodsId);
    }

    /**
     * 真正意义上的减库存，使用事务
     *  1. 释放一个锁定库存
     *  2. 减库存
     */
    @Override
    @Transactional
    public void reduceStock(String goodsId) {
        goodsDao.reduceCachedStock(goodsId);
        goodsDao.reduceStock(goodsId);
        String goodsListKey = RedisPreffix.GOODS_LIST_PREFFIX;
        GoodsList goodsVo = (GoodsList) redisTemplate.opsForHash().get(goodsListKey, goodsId);
        if(goodsVo != null) {
            goodsVo.setGoodsStock(goodsVo.getGoodsStock() - 1);
            redisTemplate.opsForHash().put(goodsListKey, goodsId, goodsVo);
        }
        String goodsKey = RedisPreffix.GOODS_PREFFIX.concat(goodsId);
        Goods goods = (Goods) redisTemplate.opsForValue().get(goodsKey);
        if(goods != null) {
            goods.setGoodsStock(goods.getGoodsStock() - 1);
            redisTemplate.opsForValue().set(goodsKey, goods);
        }
    }

    @Override
    public void checkSeckillStatus(String goodsId) {
        long remain = getRemainSeconds(goodsId);
        if(remain > 0) {
            throw new GoodsException(CodeMessage.NOT_AT_SECKILL_TIME);
        } else if(remain < 0) {
            Goods goods = getGoods(goodsId);
            long endTime = goods.getEndTime().getTime();
            long cur = System.currentTimeMillis();
            if(cur - endTime > 0) {
                throw new GoodsException(CodeMessage.SECKILL_OUT_OF_DATE);
            }
        }
    }

    @Override
    public long getRemainSeconds(String goodsId) {
        Goods goods = getGoods(goodsId);
        if(goods == null) {
            throw new GoodsException(CodeMessage.GOODS_NOT_EXISTS);
        }
        long startTime = goods.getStartTime().getTime();
        long now = System.currentTimeMillis();
        long remain = startTime - now;
        if(remain > 0) {
            remain /= 1000;
        }
        return remain;
    }

    @Override
    public Result getSeckillStatus(String goodsId) {
        long remain = getRemainSeconds(goodsId);
        if(remain > 0) {
            return ResultUtil.success(remain);
        }
        long now = System.currentTimeMillis();
        long end = getGoods(goodsId).getEndTime().getTime();
        if(now - end > 0) {
            return ResultUtil.error(CodeMessage.SECKILL_OUT_OF_DATE);
        }
        return ResultUtil.error(CodeMessage.IS_SECKILLING);
    }

    @Autowired
    LockUtil lockUtil;
    private static int count = 0, req = 0, mod = 0;
    Lock lock = new ReentrantLock();

    @Override
    public String testApi() {
//        if(lock.tryLock()) {
//            lock.lock();
//            if((Integer) redisTemplate.opsForValue().get("seckill_stock_1") > 0) {
//                redisTemplate.opsForValue().decrement("seckill_stock_1");
//                mod++;
//            }
//            lock.unlock();
//        }
//        System.out.println(mod);
//        return "ok";
        req++;
        String uuid = UUID.randomUUID().toString();
        int cnt = 5;
        while(cnt-- > 0) {
            System.out.println(uuid + "尝试： " + (5 - cnt));
            if(lockUtil.lock("seckill_lock", uuid, 1000L)) {
                count++;
                System.out.println(uuid + "获得锁");
                if((Integer) redisTemplate.opsForValue().get("seckill_stock_1") > 0) {
                    redisTemplate.opsForValue().decrement("seckill_stock_1");
                    mod++;
                }

                lockUtil.unlock("seckill_lock", uuid);
                break;
            }
        }
        System.out.println("成功" + count);
        System.out.println("总" + req);
        System.out.println("修改" + mod);
        return uuid;
    }
}
